#ifndef __VEC_CORE_H__
#define __VEC_CORE_H__
#include <initializer_list>
#include <algorithm>
#include <cmath>
#include <cassert>
#include <cstdint>
#include <array>
#include "math_tool.h"

namespace detail {

template <class T, size_t Size>
class Vec {
public:
    static constexpr size_t size = Size;

    std::array<T, Size> data;
};
} // namespace detail

template <class T, class U, size_t Size>
auto operator + (const detail::Vec<T, Size>& v1, const detail::Vec<U, Size>& v2) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size> 
{
    detail::Vec<std::common_type_t<T, U>, Size> result;
    for (size_t i = 0; i < Size; ++i) {
        result[i] = v1[i] + v2[i];
    }
    return result;
}

template <class T, class U, size_t Size>
auto operator - (const detail::Vec<T, Size>& v1, const detail::Vec<U, Size>& v2) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size> 
{
    detail::Vec<std::common_type_t<T, U>, Size> result;
    for (size_t i = 0; i < Size; ++i) {
        result[i] = v1[i] - v2[i];
    }
    return result;
}

template <class T, class U, size_t Size>
auto operator * (const detail::Vec<T, Size>& vec1, const detail::Vec<U, Size>& vec2) noexcept
-> std::common_type_t<T, U>
{
    auto result = static_cast<std::common_type_t<T, U>>(0);
    for (size_t i = 0; i < Size; ++i) {
        result += vec1[i] * vec2[i];
    }
    return result;
}

template <class T, class U, size_t Size>
auto operator * (const detail::Vec<T, Size>& vec, U value) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size> 
{
    detail::Vec<std::common_type_t<T, U>, Size> result;
    for (size_t i = 0; i < Size; ++i) {
        result[i] = vec[i] * value;
    }
    return result;
}

template <class T, class U, size_t Size>
auto operator * (T value, const detail::Vec<U, Size>& vec) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size> 
{
    return vec * value;
}

template <class T, class U, size_t Size>
auto operator / (const detail::Vec<T, Size>& vec, U value) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size> 
{
    assert(value != (U)0);
    return vec * (U(1) / value);
}

template <class T, size_t Size>
typename std::enable_if<std::numeric_limits<T>::is_integer, bool>::type
operator == (const detail::Vec<T, Size>& lhs, const detail::Vec<T, Size>& rhs) noexcept {
    for (int i = 0; i < Size; ++i) {
        if (lhs[i] != rhs[i]) {
            return false;
        }
    }
    return true;
}

template <class T, size_t Size>
typename std::enable_if<!std::numeric_limits<T>::is_integer, bool>::type
operator == (const detail::Vec<T, Size>& lhs, const detail::Vec<T, Size>& rhs) noexcept {
    for (int i = 0; i < Size; ++i) {
        if (!almost_equal(lhs.data[i], rhs.data[i])) {
            return false;
        }
    }
    return true;
}

template <class T, size_t Size>
bool operator != (const detail::Vec<T, Size>& lhs, const detail::Vec<T, Size>& rhs) noexcept {
    return !(lhs == rhs);
}

template <class T, size_t Size>
T length(const detail::Vec<T, Size>& vec) noexcept {
    T result = 0.0f;
    for (size_t i = 0; i < Size; ++i) {
        result += vec[i] * vec[i];
    }
    return std::sqrt(result);
}

template <class T, size_t Size>
detail::Vec<T, Size> normalize(const detail::Vec<T, Size>& vec) noexcept {
    return vec / length(vec);
}

template <class T, class U, size_t Size>
auto lerp(const detail::Vec<T, Size>& p1, const detail::Vec<T, Size>& p2, U t) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size>
{
    return p1 + (p2 - p1) * t;
}

template <class T, size_t Size>
detail::Vec<T, Size> abs(const detail::Vec<T, Size>& vec) noexcept {
    detail::Vec<T, Size> result;
    for (int i = 0; i < Size; ++i) {
        result = std::abs(vec.data[i]);
    }
    return result;
}

template <class T, class U, size_t Size>
detail::Vec<T, Size> vector_cast(const detail::Vec<U, Size>& vec) {
    detail::Vec<T, Size> result;
    for (int i = 0; i < Size; ++i) {
        result.data[i] = static_cast<U>(vec.data[i]);
    }
    return result;
}

template <class T, class U, size_t Size>
auto colorMix(const detail::Vec<T, Size>& color1, const detail::Vec<U, Size>& color2) noexcept
-> detail::Vec<std::common_type_t<T, U>, Size> {
    detail::Vec<std::common_type_t<T, U>, Size> result;
    for (int i = 0; i < Size; ++i) {
        result.data[i] = color1.data[i] * color2.data[i];
    }
    return result;
}

#endif